package monash.med.crystallography.DataSetTools.FileSplitter;

import datasets.jaxb.Datasets;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;

/**
 * FileSplitter by Steve Androulakis - Splits a file into separate files at the specified size
 * <p>
 * Use static method splitFile(file[, outputDir, volumeSize])
 * where file is the file to be split, outputDir (optional) is the output
 * directory for the split set, and volumeSize (optional) is the
 * split file size (in mb)
 * 
 * @author Steve Androulakis (Monash University)
 * @version 1.0
*/	
public class FileSplitter {
	
	/**
	 * @param  fileName The name of the file that is to be split
	 * @param	outputDir	The output directory of the split set
	 * @param  volumeSize The size the file is to be split by (in megabytes)
	 */
	public static ArrayList<Datasets.Dataset.Files.File> splitFile(String fileName, String outputDir, float volumeSize)
	{
		return split(fileName, outputDir, (long)(volumeSize * 1024 * 1024));
	}
	
	/**
	 * @param  fileName The name of the file that is to be split
	 * @param	outputDir	The output directory of the split set
	 */
	public static ArrayList<Datasets.Dataset.Files.File> splitFile(String fileName, String outputDir)
	{
		return split(fileName, outputDir, (500 * 1024 * 1024));
	}	
	
	/**
	 * @param  fileName The name of the file that is to be split
	 * @param  volumeSize The size the file is to be split by (in megabytes)
	 */
	public static ArrayList<Datasets.Dataset.Files.File> splitFile(String fileName, float volumeSize)
	{
		return split(fileName, new File(fileName).getParent(), (long)(volumeSize * 1024 * 1024));
	}
	
	/**
	 * @param  fileName The name of the file that is to be split
	 */
	public static ArrayList<Datasets.Dataset.Files.File> splitFile(String fileName)
	{
		return split(fileName, new File(fileName).getParent(), (long)(500 * 1024 * 1024));
	}
	
	private static ArrayList<Datasets.Dataset.Files.File> split(String fileName, String outputDir, long volumeSize)
	{	
                ArrayList<Datasets.Dataset.Files.File> fileAdd = new ArrayList<Datasets.Dataset.Files.File>();
		
		try {	
						
			File archiveFileF = new File(fileName);	
			
			if(!archiveFileF.exists())
			{
				throw new FileNotFoundException();
			}
			
			int parts = Math.round(archiveFileF.length() / volumeSize) + 1;
			
			System.out.println("Parts to be split: " + parts);
			
			for(long i = 0; (i * volumeSize) < archiveFileF.length(); i++) // for each file split
			{
				long offset = (i * volumeSize);
							
				// create output directories if they don't exist
				new File(new File(outputDir).getAbsolutePath() + File.separator).mkdirs();
				
				// create output file for writing
				File fileOut = new File(new File(outputDir).getAbsolutePath() + File.separator + archiveFileF.getName() + "." + i);
				
				if(fileOut.exists())
				{
					throw new IOException("File " + fileOut.getName() + " already exists. Exiting");
				}
				
				FileOutputStream fileOutputStream;
	
				fileOutputStream = new FileOutputStream(fileOut, true);
		
				FileChannel fout = fileOutputStream.getChannel();
				//fout.write(mbb);
				System.out.println("Splitting... " + fileOut.getName());
					
					if(offset+volumeSize < archiveFileF.length()) // write file parts
					{
						FileInputStream fileInputStream = new FileInputStream(archiveFileF);
						FileChannel finc = fileInputStream.getChannel();
                                               // System.out.println(offset + " v" + volumeSize);
						MappedByteBuffer mbb = finc.map(FileChannel.MapMode.READ_ONLY, offset,
								volumeSize);
                                                // failing here on tar.bz.2 with Negative position
                                                // maybe 'cause offset is an int. write something that just splits
                                                // files
                                                
                                                // changed to long and re-ran test.
						finc.close();
					
						fout.write(mbb);
					}
					else // if last chunk in the split
					{
						long readToEOF = (long)archiveFileF.length() - offset;
	
						FileInputStream fileInputStream = new FileInputStream(archiveFileF);
						FileChannel finc = fileInputStream.getChannel();
						MappedByteBuffer mbb = finc.map(FileChannel.MapMode.READ_ONLY, offset,
								readToEOF);
						finc.close();
					
						fout.write(mbb);
					}
	
				fout.close();
                                
                                Datasets.Dataset.Files.File file = new Datasets.Dataset.Files.File();
                                file.setFileName(fileOut.getName());
                                file.setFileSize(String.valueOf(fileOut.length()));
                                
                                fileAdd.add(file);
			}
		
			System.out.println("Finished Splitting To: " + outputDir);
			
		} catch (FileNotFoundException e) {
			System.out.println("ERROR: File not found");
		}			
		catch (IOException e)
		{
			System.out.println(e.getMessage());
			System.out.println("ERROR: Failed or interrupted file operation");
		}
		catch (Exception e)
		{
			System.out.println("ERROR: Stack trace as follows...");
			e.printStackTrace();
		}
		
		return fileAdd;
	}
	
	/**
	 * The commandline implementaton of the {@link FileSplitter} class.
	 * <p>
	 * Run <i>java -jar FileSplitter.jar</i> for instructions on usage. 
	 */	
	public static void main(String[] args) {
		
		if(args.length == 1)
		{
			FileSplitter.splitFile(args[0]);
		}
		else if(args.length == 2)
		{
			try{
				FileSplitter.splitFile(args[0], Float.parseFloat(args[1])); // if the second option is the volume size
			}
			catch(NumberFormatException e)
			{
				FileSplitter.splitFile(args[0], args[1]); // if the second option is the output dir
			}
		}
		else if(args.length == 3)
		{
			FileSplitter.splitFile(args[0], args[1], Float.parseFloat(args[2]));
		}
		else
		{
			System.out.println("FileSplitter by Steve Androulakis - For splitting large datasets into more manageable pieces");
			System.out.println("");
			System.out.println("USAGE:");
			System.out.println("FileSplitter <archivePath> <outputDir> <volumesize>");
			System.out.println("");
			System.out.println("archivePath: The absolute or program-relative path to the file to be split");
			System.out.println("outputDir (optional): The absolute or program-relative path to the directory the files will be split to");
			System.out.println("	omit this and the output directory will be created alongside the file to be split");
			System.out.println("volumesize (optional): The size to split the file by, in megabytes (supports decimal numbers)");
			System.out.println("	omit this and it uses the default value of 500mb");
		}
		
	}

}